﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Green;

namespace Noris.Schedule.Support.Components
{
    #region class ClassTreePanel : panel, který zobrazuje strom tříd a atributy
    /// <summary>
    /// Třída pro zobrazení panelu se stromem tříd a jejich atributů
    /// </summary>
    [DefaultEvent("AttributeSelect")]
    public class ClassTreePanel : DSplitContainer
    {
        #region Fyzická konstrukce
        public ClassTreePanel()
        {
            this.Init();
        }
        protected void Init()
        {
            this._SplitAttr = new SplitContainer();
            this._ImageList = new ImageList();
            this._ClassTree = new TreeView() { BorderStyle = BorderStyle.None, Dock = DockStyle.Fill, TabIndex = 0, TabStop = true };
            this._AttrList = new ListView() { BorderStyle = BorderStyle.None, Dock = DockStyle.Fill, TabIndex = 1, TabStop = true };
            this._SelectBtn = new Button() { Text = "Select", Dock = DockStyle.Fill, UseVisualStyleBackColor = true, TabIndex = 2, TabStop = true, TextImageRelation = TextImageRelation.ImageBeforeText, ImageAlign = ContentAlignment.MiddleRight, TextAlign = ContentAlignment.MiddleCenter };

            // Begin, Suspend:
            ((System.ComponentModel.ISupportInitialize)(this._SplitAttr)).BeginInit();
            this._SplitAttr.Panel1.SuspendLayout();
            this._SplitAttr.Panel2.SuspendLayout();
            this._SplitAttr.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this)).BeginInit();
            this.Panel1.SuspendLayout();
            this.Panel2.SuspendLayout();
            this.SuspendLayout();

            // this property:
            this.BorderStyle = BorderStyle.Fixed3D;
            this.FixedPanel = FixedPanel.None;
            this.IsSplitterFixed = false;
            this.Orientation = Orientation.Vertical;
            this.Size = new Size(480, 350);
            this.SplitterDistance = 220;

            // Naplnění Main splitteru (tree + atributy):
            this.Panel1.Controls.Add(this._ClassTree);
            this.Panel2.Controls.Add(this._SplitAttr);

            // SplitAttr:
            this._SplitAttr.Bounds = this.Panel2.ClientRectangle;
            this._SplitAttr.Panel1.Controls.Add(this._AttrList);
            this._SplitAttr.Panel2.Controls.Add(this._SelectBtn);
            this._SplitAttr.BorderStyle = BorderStyle.Fixed3D;
            this._SplitAttr.Dock = DockStyle.Fill;
            this._SplitAttr.FixedPanel = FixedPanel.Panel2;
            this._SplitAttr.IsSplitterFixed = true;
            this._SplitAttr.Orientation = Orientation.Horizontal;
            this._SplitAttr.Panel2MinSize = 30;
            this._SplitAttr.Panel2.Padding = new Padding(2);
            this._SplitAttr.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
            this._SplitAttr.SplitterDistance = 295;
            this._SplitAttr.TabIndex = 0;

            // _ImageList:
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TORIGHT_MASTER_TYPE, PicLibrary16.Rel_srmt_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TORIGHT_MASTER_NONTYPE, PicLibrary16.Rel_srmn_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TORIGHT_ENTRIES_TYPE, PicLibrary16.Rel_sret_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TORIGHT_ENTRIES_NONTYPE, PicLibrary16.Rel_sren_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TOLEFT_MASTER_TYPE, PicLibrary16.Rel_slmt_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TOLEFT_MASTER_NONTYPE, PicLibrary16.Rel_slmn_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TOLEFT_ENTRIES_TYPE, PicLibrary16.Rel_slet_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_STATIC_TOLEFT_ENTRIES_NONTYPE, PicLibrary16.Rel_slen_FromFile);

            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TORIGHT_MASTER_TYPE, PicLibrary16.Rel_drmt_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TORIGHT_MASTER_NONTYPE, PicLibrary16.Rel_drmn_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_TYPE, PicLibrary16.Rel_dret_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_NONTYPE, PicLibrary16.Rel_dren_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TOLEFT_MASTER_TYPE, PicLibrary16.Rel_dlmt_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TOLEFT_MASTER_NONTYPE, PicLibrary16.Rel_dlmn_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_TYPE, PicLibrary16.Rel_dlet_FromFile);
            this._ImageList.Images.Add(IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_NONTYPE, PicLibrary16.Rel_dlen_FromFile);

            this._ImageList.Images.Add(IMAGEKEY_CLASS, PicLibrary16.Book2_FromFile);                         // Image pro třídu root nebo navázanou z netypového vztahu
            this._ImageList.Images.Add(IMAGEKEY_CLASS_ACTIVE, PicLibrary16.BookOpen_FromFile);               // Image pro aktivní node, pro třídu root nebo navázanou v hlavičkovém vztahu
            this._ImageList.Images.Add(IMAGEKEY_EXTEND_RELCLASS, PicLibrary16.Book1_FromFile);               // Image pro třídu navázanou přímo z nadřízené třídy vztahem Extended
            this._ImageList.Images.Add(IMAGEKEY_EXTEND_RELCLASS_ACTIVE, PicLibrary16.Book1Open_FromFile);    // Image pro aktivní node pro třídu navázanou přímo z nadřízené třídy vztahem Extended
            this._ImageList.Images.Add(IMAGEKEY_MASTER_RELCLASS_STAT_L, PicLibrary16.Book2_FromFile);        // Image pro třídu navázanou přímo z nadřízené třídy vztahem master - statický, zleva doprava (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_MASTER_RELCLASS_STAT_R, PicLibrary16.Book2R_FromFile);       // Image pro třídu navázanou přímo z nadřízené třídy vztahem master - statický, zprava doleva (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_MASTER_RELCLASS_DYN_L, PicLibrary16.Book2Link_FromFile);     // Image pro třídu navázanou přímo z nadřízené třídy vztahem master - dynamický, zleva doprava (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_MASTER_RELCLASS_DYN_R, PicLibrary16.Book2Link_FromFile);     // Image pro třídu navázanou přímo z nadřízené třídy vztahem master - dynamický, zprava doleva (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_ENTRY_RELCLASS_STAT, PicLibrary16.Book3_FromFile);           // Image pro třídu navázanou přímo z nadřízené třídy vztahem entry - statický, pouze zleva doprava (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_ENTRY_RELCLASS_DYN, PicLibrary16.Book3Link_FromFile);        // Image pro třídu navázanou přímo z nadřízené třídy vztahem entry - dynamický (v typovém vztahu)
            this._ImageList.Images.Add(IMAGEKEY_ENTRY_ACTIVE, PicLibrary16.Book3Open_FromFile);              // Image pro aktivní node, pro třídu navázanou v položkovém vztahu
            this._ImageList.Images.Add(IMAGEKEY_RELATION_STAT_L, PicLibrary16.CodeClass_FromFile);           // Image pro zobrazení statického vztahu, zleva doprava, který je netypový (na této úrovni lze vybírat z lcs.subjekty)
            this._ImageList.Images.Add(IMAGEKEY_RELATION_STAT_R, PicLibrary16.CodeClassR_FromFile);          // Image pro zobrazení statického vztahu, zprava doleva, který je netypový (na této úrovni lze vybírat z lcs.subjekty)
            this._ImageList.Images.Add(IMAGEKEY_RELATION_DYN, PicLibrary16.Link_FromFile);                   // Image pro zobrazení dynamického vztahu, který je netypový (na této úrovni lze vybírat z lcs.subjekty)

            this._ImageList.Images.Add(IMAGEKEY_WAIT, PicLibrary16.HourGlass_FromFile);                      // Image pro zobrazení přesýpacích digitálních hodinek
            this._ImageList.ImageSize = new Size(16, 16);

            // Tree property:
            this._ClassTree.FullRowSelect = true;
            this._ClassTree.HideSelection = false;
            this._ClassTree.CheckBoxes = false;
            this._ClassTree.Indent = 18;
            this._ClassTree.ImageList = this._ImageList;
            this._ClassTree.LabelEdit = false;
            this._ClassTree.PathSeparator = ".";
            this._ClassTree.Scrollable = true;
            this._ClassTree.ShowLines = true;
            this._ClassTree.ShowNodeToolTips = true;
            this._ClassTree.ShowPlusMinus = true;
            this._ClassTree.ShowRootLines = true;

            // List property:
            this._AttrList.FullRowSelect = true;
            this._AttrList.GridLines = false;
            this._AttrList.HideSelection = false;
            this._AttrList.LabelEdit = false;
            this._AttrList.LabelWrap = false;
            this._AttrList.MultiSelect = true;
            this._AttrList.Scrollable = true;
            this._AttrList.ShowGroups = true;
            this._AttrList.ShowItemToolTips = true;
            this._AttrList.View = View.Details;

            // List columns & groups:
            this._AttrList.Columns.Clear();
            this._AttrList.Columns.Add(new ColumnHeader() { Text = "Název atributu", TextAlign = HorizontalAlignment.Left, Width = 120 });
            this._AttrList.Columns.Add(new ColumnHeader() { Text = "Name", TextAlign = HorizontalAlignment.Left, Width = 180 });
            this._AttrList.Groups.Clear();
            this._AttrList.Groups.Add(new ListViewGroup() { Header = "Rozšířená data", HeaderAlignment = HorizontalAlignment.Left, Name = ClassExtendAttribute.ATTRIBUTEGROUP_EXTENDED });
            this._AttrList.Groups.Add(new ListViewGroup() { Header = "Hlavičkové atributy", HeaderAlignment = HorizontalAlignment.Left, Name = ClassExtendAttribute.ATTRIBUTEGROUP_MASTER });
            this._AttrList.Groups.Add(new ListViewGroup() { Header = "Položkové atributy", HeaderAlignment = HorizontalAlignment.Left, Name = ClassExtendAttribute.ATTRIBUTEGROUP_ENTRIES });
            this._AttrList.Groups.Add(new ListViewGroup() { Header = "Systém", HeaderAlignment = HorizontalAlignment.Left, Name = ClassExtendAttribute.ATTRIBUTEGROUP_SYSTEM });

            // Registrace interních eventhandlerů:
            this._ClassTree.AfterSelect += new TreeViewEventHandler(_ClassTree_AfterSelect);
            this._ClassTree.BeforeExpand += new TreeViewCancelEventHandler(_ClassTree_BeforeExpand);
            this._ClassTree.DrawMode = TreeViewDrawMode.OwnerDrawAll;          // Zajistíme si, že před vykreslením nodu se na něj podíváme v metodě _ClassTree_DrawNode():
            this._ClassTree.DrawNode += new DrawTreeNodeEventHandler(_ClassTree_DrawNode);
            this._AttrList.DoubleClick += new EventHandler(_AttrList_DoubleClick);
            this._SelectBtn.Click += new EventHandler(_SelectBtn_Click);

            // Resume, End:
            this.Panel1.ResumeLayout(false);
            this.Panel2.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this)).EndInit();

            this._SplitAttr.Panel1.ResumeLayout(false);
            this._SplitAttr.Panel2.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this._SplitAttr)).EndInit();
            this._SplitAttr.ResumeLayout(false);

            this.ResumeLayout(false);
        }
        private System.Windows.Forms.SplitContainer _SplitAttr;
        private System.Windows.Forms.ImageList _ImageList;
        private System.Windows.Forms.TreeView _ClassTree;
        private System.Windows.Forms.ListView _AttrList;
        private System.Windows.Forms.Button _SelectBtn;
        private ClassTreeNode _ClassTreeRootNode;
        #endregion
        #region Vnitřní události
        /// <summary>
        /// Tato metoda se volá při každém kreslení nodu.
        /// Zde ji využijeme pro zajištění toho, že node bude mít načteny všechny svoje (sub)nodes.
        /// Kreslení necháme na systému.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _ClassTree_DrawNode(object sender, DrawTreeNodeEventArgs e)
        {
            //ClassTreeNode classTreeNode = e.Node as ClassTreeNode;
            //if (classTreeNode != null)
            //    classTreeNode.CheckLoadedChilds();
            e.DrawDefault = true;
        }
        /// <summary>
        /// Double click na položce atributu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _AttrList_DoubleClick(object sender, EventArgs e)
        {
            this.CallAttributeSelect();
        }
        /// <summary>
        /// Click na buttonu Select
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _SelectBtn_Click(object sender, EventArgs e)
        {
            this.CallAttributeSelect();
        }
        /// <summary>
        /// Před rozbalením položky Tree
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _ClassTree_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            ClassTreeNode classTreeNode = e.Node as ClassTreeNode;
            if (classTreeNode != null)
                classTreeNode.CheckLoadedChilds();
        }
        /// <summary>
        /// Naplní tento objekt daty pro root třídu
        /// </summary>
        /// <param name="value"></param>
        private void _SetRootClassNumber(int classNumber)
        {
            this._ClassTree.Nodes.Clear();
            this._ClassTreeRootNode = null;
            this._RootClassNumber = classNumber;
            if (classNumber != 0)
            {
                this._ClassTreeRootNode = new ClassTreeNode(this, ClassTreeNode.ClassTreeNodeType.Class, classNumber);
                this._ClassTree.Nodes.Add(this._ClassTreeRootNode);
                this._ClassTree.SelectedNode = this._ClassTreeRootNode;
            }
        }
        /// <summary>
        /// Po výběru položky Tree
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _ClassTree_AfterSelect(object sender, TreeViewEventArgs e)
        {
            ClassTreeNode classTreeNode = e.Node as ClassTreeNode;
            this.SelectedClassNode = classTreeNode;
            this.FillAttributeListWhenNeed(classTreeNode);
        }
        /// <summary>
        /// Naplní seznam atributů pro danou položku Tree
        /// </summary>
        /// <param name="classTreeNode"></param>
        void FillAttributeListWhenNeed(ClassTreeNode classTreeNode)
        {
            if (classTreeNode == null) return;
            ClassTreeNode lastTreeNode = this._AttributeListFilledForNode;
            if (lastTreeNode != null && lastTreeNode.FullTreePath == classTreeNode.FullTreePath) return;
            classTreeNode.SendAttributeList();
        }
        /// <summary>
        /// Naplní seznam atributů pro danou položku Tree.
        /// Tuto metodu je možno volat z threadu na pozadí, zajistí si invokaci GUI threadu.
        /// </summary>
        /// <param name="classTreeNode"></param>
        internal void FetchAttributeList(ClassTreeNode classTreeNode)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<ClassTreeNode>(this.FetchAttributeList), classTreeNode);
            else
            {
                this._AttrList.Items.Clear();
                this._AttrList.Items.AddRange(classTreeNode.GetAttributesItem(this._AttrList));
                this._AttributeListFilledForNode = classTreeNode;
            }
        }
        private ClassTreeNode _AttributeListFilledForNode;
        /// <summary>
        /// Pokud je vybraná nějaká (jedna nebo více) položek atributů, zavolá událost AttributeSelect
        /// </summary>
        protected void CallAttributeSelect()
        {
            if (this.AttributeSelect == null) return;

            List<ClassTreeAttributeItem> attributes = this._GetSelectedAttributes();
            if (attributes.Count == 0) return;

            ClassTreePanelArgs args = new ClassTreePanelArgs(this.SelectedClassNode, attributes);

            this.AttributeSelect(this, args);
        }
        /// <summary>
        /// Vrátí soupis atributů, jež jsou právě nyní vybrány v seznamu.
        /// </summary>
        /// <returns></returns>
        private List<ClassTreeAttributeItem> _GetSelectedAttributes()
        {
            List<ClassTreeAttributeItem> attributes = new List<ClassTreeAttributeItem>();
            foreach (ListViewItem item in this._AttrList.SelectedItems)
            {
                ClassTreeAttributeItem attribute = item.Tag as ClassTreeAttributeItem;
                if (attribute != null)
                    attributes.Add(attribute);
            }
            return attributes;
        }
        #endregion
        #region Public property a eventy
        /// <summary>
        /// Číslo třídy v rootu stromu
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int RootClassNumber
        {
            get { return this._RootClassNumber; }
            set { this._SetRootClassNumber(value); }
        }
        private int _RootClassNumber;
        /// <summary>
        /// Image na buttonu Select
        /// </summary>
        [Category("ClassTreePanel")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Image na buttonu Select")]
        public Image ButtonImage
        {
            get { return this._SelectBtn.Image; }
            set { this._SelectBtn.Image = value; }
        }
        /// <summary>
        /// Text na buttonu Select
        /// </summary>
        [Category("ClassTreePanel")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Text na buttonu Select")]
        public string ButtonText
        {
            get { return this._SelectBtn.Text; }
            set { this._SelectBtn.Text = value; }
        }
        /// <summary>
        /// Událost, kterou volá ClassTreePanel poté, kdy uživatel klikne na tlačítko Select nebo dá dvojklik na položce seznamu atributů
        /// </summary>
        [Category("ClassTreePanel")]
        [Description("Událost volaná po výběru atributu(atributů) a po doubleclicku nebo clicku na tlačítko Select")]
        public event ClassTreePanelHandler AttributeSelect;
        /// <summary>
        /// Objekt umožňující rozšiřovat strom tříd o další třídy, vztahy a atributy.
        /// Objekt je nutno setovat ještě před setováním RootClassNumber, 
        /// protože při vložení hodnoty do RootClassNumber se začínají načítat data pro tuto třídu,
        /// a v tu chvíli je třeba již mít přístup na ClassTreeExtender.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IClassTreeExtender ClassTreeExtender { get; set; }
        /// <summary>
        /// Aktuálně vybraná položka ve stromu = její atributy jsou načteny v seznamu
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ClassTreeNode SelectedClassNode { get; private set; }
        /// <summary>
        /// Obsahuje soupis atributů, jež jsou právě nyní vybrány v seznamu.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public List<ClassTreeAttributeItem> SelectedAttributes { get { return this._GetSelectedAttributes(); } }
        /// <summary>
        /// Příznak, zda se mají nabízet i statické vztahy zprava doleva (zobrazované na dolních záložkách)
        /// </summary>
        [Category("ClassTreePanel")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Nabízet i statické vztahy zprava")]
        public bool RelationToLeftEnabled { get; set; }
        #endregion
        #region Public metody
        /// <summary>
        /// Zajistí otevření Tree na daném nodu a výběr zadaných atributů
        /// </summary>
        /// <param name="keys"></param>
        public void SelectAttributes(List<string> keys)
        {
            string treeNodeFullPath;
            List<string> attributeList;
            this._SepareTreeAttList(keys, out treeNodeFullPath, out attributeList);
            if (treeNodeFullPath == null) return;

            this._ClassTreeCloseAllNodes(treeNodeFullPath);
            ClassTreeNode classTreeNode = this._ClassTreeExpandToFullPath(treeNodeFullPath);
            if (classTreeNode == null) return;

            this._ClassTree.SelectedNode = classTreeNode;
            this._ClassTree.SelectedNode.EnsureVisible();
            this.FillAttributeListWhenNeed(classTreeNode);

            this._AttrListSelectItems(attributeList);
        }
        /// <summary>
        /// Zajistí otevření ClassTree tak, aby byl viditelný poslední zadaný node, ten selectuje.
        /// </summary>
        /// <param name="treeNodeFullPath"></param>
        private ClassTreeNode _ClassTreeExpandToFullPath(string treeNodeFullPath)
        {
            if (String.IsNullOrEmpty(treeNodeFullPath)) return null;
            ClassTreeNode classTreeNode = this._ClassTreeRootNode;
            if (classTreeNode == null) return null;

            string[] items = treeNodeFullPath.Split(':');         // "C1180:R105125:C1189:R9260:C343" rozdělit v místě dvojtečky
            int itemCount =  items.Length;
            if (itemCount == 0) return null;

            if (classTreeNode.TreePath != items[0]) return null;  // Root node není odpovídající třídy
            
            for (int i = 1; i < itemCount; i++)
            {
                string key1 = items[i];                           // "C1180", "R105125", ...
                string key2 = null;
                if (i < (itemCount - 1) && (key1[0] == 'R' || key1[0] == 'L') && items[i + 1][0] == 'C')
                    key2 = key1 + ":" + items[i + 1];             // "R105125:C1189"

                classTreeNode.CheckLoadedChilds();
                classTreeNode.Expand();
                List<ClassTreeNode> nodes = this._GetNodesAsClassTreeNodes(classTreeNode.Nodes);
                ClassTreeNode subNode = nodes.FirstOrDefault(n => n.TreePath == key1 || n.TreePath == key2);
                if (subNode == null) return null;

                classTreeNode = subNode;
                if (subNode.TreePath.Contains(":"))
                    i++;
            }
            return classTreeNode;
        }
        /// <summary>
        /// Zajistí selectování atributů podle daného soupisu jejich názvů.
        /// Seznam atributů musí být předem načten, k tomu oslouží metoda this.FillAttributeList(ClassTreeNode treeNode).
        /// Soupis názvů má položky
        /// </summary>
        /// <param name="attributeList"></param>
        private void _AttrListSelectItems(List<string> attributeList)
        {
            ListViewItem topItem = null;
            foreach (ListViewItem listItem in this._AttrList.Items)
            {
                bool selected = false;

                ClassTreeAttributeItem attrItem = listItem.Tag as ClassTreeAttributeItem;
                if (attrItem != null)
                    selected = (attributeList.Any(s => s == attrItem.AttributeKey));

                listItem.Selected = selected;

                if (selected && (topItem == null || (topItem != null && topItem.Index > listItem.Index)))
                    topItem = listItem;
            }
            if (topItem != null)
                topItem.EnsureVisible();
        }
        /// <summary>
        /// Z dodané kolekce nodů (TreeNodeCollection) vrátí seznam nodů typu ClassTreeNode.
        /// </summary>
        /// <param name="nodes"></param>
        /// <returns></returns>
        private List<ClassTreeNode> _GetNodesAsClassTreeNodes(TreeNodeCollection nodes)
        {
            List<ClassTreeNode> result = new List<ClassTreeNode>();
            foreach (TreeNode node in nodes)
            {
                ClassTreeNode classTreeNode = node as ClassTreeNode;
                if (classTreeNode != null)
                    result.Add(classTreeNode);
            }
            return result;
        }
        /// <summary>
        /// Seznam obsahující klíče atributů (ClassTreeAttributeItem.FullTreePath) rozebere na FullTreePath do stromu tříd 
        /// a odpovídajícího seznamu atributů, které pocházejí z této třídy.
        /// Pokud seznam klíčů obsahuje atributy z více tříd, pak přebírá jen atributy z první z nich.
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="treeNodeFullPath"></param>
        /// <param name="attributeList"></param>
        private void _SepareTreeAttList(List<string> keys, out string treeNodeFullPath, out List<string> attributeList)
        {
            treeNodeFullPath = null;
            attributeList = new List<string>();
            string separator = ClassTreeAttributeItem.ATTRIBUTE_SEPARATOR;
            foreach (string key in keys)
            {
                if (String.IsNullOrEmpty(key)) continue;
                int keyLen = key.Length;
                int at = key.IndexOf(separator);
                if (at < 2 || at > (keyLen - 3)) continue;
                string treePath = key.Substring(0, at).Trim();
                string attrName = key.Substring(at + separator.Length);
                if (treeNodeFullPath == null)
                    treeNodeFullPath = treePath;
                if (treePath == treeNodeFullPath)
                    attributeList.Add(attrName);
            }
        }
        /// <summary>
        /// Zajistí zavření všech otevřených nodů.
        /// </summary>
        private void _ClassTreeCloseAllNodes()
        {
            this._ClassTreeCloseAllNodes(this._ClassTree.Nodes, null);
        }
        /// <summary>
        /// Zajistí zavření všech otevřených nodů.
        /// </summary>
        private void _ClassTreeCloseAllNodes(string excludeFullPath)
        {
            this._ClassTreeCloseAllNodes(this._ClassTree.Nodes, excludeFullPath);
        }
        /// <summary>
        /// Zajistí zavření všech otevřených nodů v dodané kolekci.
        /// </summary>
        /// <param name="nodes"></param>
        private void _ClassTreeCloseAllNodes(TreeNodeCollection nodes, string excludeFullPath)
        {
            foreach (TreeNode node in nodes)
            {
                this._ClassTreeCloseAllNodes(node.Nodes, excludeFullPath);
                if (this._ClassTreeHasCloseNode(node, excludeFullPath))
                    node.Collapse();
            }
        }
        /// <summary>
        /// Zjistí, zda se má node zavřít
        /// </summary>
        /// <param name="node"></param>
        /// <param name="excludeFullPath"></param>
        /// <returns></returns>
        private bool _ClassTreeHasCloseNode(TreeNode node, string excludeFullPath)
        {
            if (!node.IsExpanded) return false;
            if (String.IsNullOrEmpty(excludeFullPath)) return true;
            ClassTreeNode classTreeNode = node as ClassTreeNode;
            if (node == null) return true;
            string fullPath = classTreeNode.FullTreePath;     // "C1180:R2564:C1189"
            bool collapse = (fullPath.Length >= excludeFullPath.Length || !excludeFullPath.StartsWith(fullPath));
            return collapse;
        }
        #endregion
        #region Konstanty

        /// <summary>Konstanta pro vztah: statický, doprava, z Master, typový</summary>
        internal const string IMAGE_REL_STATIC_TORIGHT_MASTER_TYPE = "rel_srmt";
        /// <summary>Konstanta pro vztah: statický, doprava, z Master, netypový</summary>
        internal const string IMAGE_REL_STATIC_TORIGHT_MASTER_NONTYPE = "rel_srmn";
        /// <summary>Konstanta pro vztah: statický, doprava, z Entries, typový</summary>
        internal const string IMAGE_REL_STATIC_TORIGHT_ENTRIES_TYPE = "rel_sret";
        /// <summary>Konstanta pro vztah: statický, doprava, z Entries, typový</summary>
        internal const string IMAGE_REL_STATIC_TORIGHT_ENTRIES_NONTYPE = "rel_sren";
        /// <summary>Konstanta pro vztah: statický, doleva, z Master, typový</summary>
        internal const string IMAGE_REL_STATIC_TOLEFT_MASTER_TYPE = "rel_slmt";
        /// <summary>Konstanta pro vztah: statický, doleva, z Master, netypový</summary>
        internal const string IMAGE_REL_STATIC_TOLEFT_MASTER_NONTYPE = "rel_slmn";
        /// <summary>Konstanta pro vztah: statický, doleva, z Entries, typový</summary>
        internal const string IMAGE_REL_STATIC_TOLEFT_ENTRIES_TYPE = "rel_slet";
        /// <summary>Konstanta pro vztah: statický, doleva, z Entries, typový</summary>
        internal const string IMAGE_REL_STATIC_TOLEFT_ENTRIES_NONTYPE = "rel_slen";

        /// <summary>Konstanta pro vztah: dynamický, doprava, z Master, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TORIGHT_MASTER_TYPE = "rel_drmt";
        /// <summary>Konstanta pro vztah: dynamický, doprava, z Master, netypový</summary>
        internal const string IMAGE_REL_DYNAMIC_TORIGHT_MASTER_NONTYPE = "rel_drmn";
        /// <summary>Konstanta pro vztah: dynamický, doprava, z Entries, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_TYPE = "rel_dret";
        /// <summary>Konstanta pro vztah: dynamický, doprava, z Entries, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_NONTYPE = "rel_dren";
        /// <summary>Konstanta pro vztah: dynamický, doleva, z Master, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TOLEFT_MASTER_TYPE = "rel_dlmt";
        /// <summary>Konstanta pro vztah: dynamický, doleva, z Master, netypový</summary>
        internal const string IMAGE_REL_DYNAMIC_TOLEFT_MASTER_NONTYPE = "rel_dlmn";
        /// <summary>Konstanta pro vztah: dynamický, doleva, z Entries, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_TYPE = "rel_dlet";
        /// <summary>Konstanta pro vztah: dynamický, doleva, z Entries, typový</summary>
        internal const string IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_NONTYPE = "rel_dlen";


        /// <summary>Konstanta pro třídu root nebo navázanou z netypového vztahu</summary>
        internal const string IMAGEKEY_CLASS = "Class";
        /// <summary>Konstanta pro aktivní node, pro třídu root nebo navázanou v hlavičkovém vztahu</summary>
        internal const string IMAGEKEY_CLASS_ACTIVE = "ClassActive";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem Extended</summary>
        internal const string IMAGEKEY_EXTEND_RELCLASS = "ExtendRel";
        /// <summary>Konstanta pro aktivní node pro třídu navázanou přímo z nadřízené třídy vztahem Extended</summary>
        internal const string IMAGEKEY_EXTEND_RELCLASS_ACTIVE = "ExtendRelActive";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem master - statický, zleva doprava (v typovém vztahu)</summary>
        internal const string IMAGEKEY_MASTER_RELCLASS_STAT_L = "MasterRelStatL";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem master - statický, zprava doleva (v typovém vztahu)</summary>
        internal const string IMAGEKEY_MASTER_RELCLASS_STAT_R = "MasterRelStatR";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem master - dynamický, zleva doprava (v typovém vztahu)</summary>
        internal const string IMAGEKEY_MASTER_RELCLASS_DYN_L = "MasterRelDynL";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem master - dynamický, zprava doleva (v typovém vztahu)</summary>
        internal const string IMAGEKEY_MASTER_RELCLASS_DYN_R = "MasterRelDynR";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem entry - statický (v typovém vztahu)</summary>
        internal const string IMAGEKEY_ENTRY_RELCLASS_STAT = "EntryRelStat";
        /// <summary>Konstanta pro třídu navázanou přímo z nadřízené třídy vztahem entry - dynamický (v typovém vztahu)</summary>
        internal const string IMAGEKEY_ENTRY_RELCLASS_DYN = "EntryRelDyn";
        /// <summary>Konstanta pro aktivní node, pro třídu navázanou v položkovém vztahu</summary>
        internal const string IMAGEKEY_ENTRY_ACTIVE = "EntryActive";
        /// <summary>Konstanta pro zobrazení statického vztahu, zleva doprava, který je netypový (na této úrovni lze vybírat z lcs.subjekty)</summary>
        internal const string IMAGEKEY_RELATION_STAT_L = "RelStatL";
        /// <summary>Konstanta pro zobrazení statického vztahu, zprava doleva, který je netypový (na této úrovni lze vybírat z lcs.subjekty)</summary>
        internal const string IMAGEKEY_RELATION_STAT_R = "RelStatR";
        /// <summary>Konstanta pro zobrazení dynamického vztahu, který je netypový (na této úrovni lze vybírat z lcs.subjekty)</summary>
        internal const string IMAGEKEY_RELATION_DYN = "RelDyn";
        /// <summary>Konstanta pro zobrazení přesýpacích digitálních hodin (namísto písku se v nich přelévají tekuté krystaly)</summary>
        internal const string IMAGEKEY_WAIT = "Wait";
        #endregion
    }
    #endregion
    #region delegate ClassTreePanelHandler, class ClassTreePanelArgs : předpisy pro události v okně ClassTreePanel
    public delegate void ClassTreePanelHandler(object sender, ClassTreePanelArgs args);
    public class ClassTreePanelArgs : EventArgs
    {
        public ClassTreePanelArgs(ClassTreeNode selectedClassNode, List<ClassTreeAttributeItem> selectedAttributes)
        {
            this.SelectedClassNode = selectedClassNode;
            this.SelectedAttributes = selectedAttributes;
        }
        public ClassTreeNode SelectedClassNode { get; private set; }
        public List<ClassTreeAttributeItem> SelectedAttributes { get; private set; }
    }
    #endregion
    #region class ClassTreeNode : jeden node, který v sobě nese navíc informaci o datech třídy / vztahu
    /// <summary>
    /// ClassTreeNode : jeden node, který v sobě nese navíc informaci o datech třídy / vztahu
    /// </summary>
    public class ClassTreeNode : TreeNode
    {
        #region Konstrukce
        /// <summary>
        /// Položka ve stromu tříd, první (root)
        /// </summary>
        /// <param name="classTreePanel">ClassTreePanel který hostí tento root prvek.</param>
        /// <param name="nodeType">Druh dat (třída / vztah)</param>
        /// <param name="nodeNumber">Číslo třídy / číslo vztahu</param>
        internal ClassTreeNode(ClassTreePanel classTreePanel, ClassTreeNodeType nodeType, int nodeNumber)
        {
            this._Init(classTreePanel, null, nodeType, nodeNumber, null, '?', Support.Green.RelationSideType.None);
        }
        /// <summary>
        /// Položka ve stromu tříd
        /// </summary>
        /// <param name="parent">Parent tohoto prvku. Root zde má null.</param>
        /// <param name="nodeType">Druh dat (třída / vztah)</param>
        /// <param name="nodeNumber">Číslo třídy / číslo vztahu</param>
        internal ClassTreeNode(ClassTreeNode parent, ClassTreeNodeType nodeType, int nodeNumber)
        {
            this._Init(parent.ClassTreePanel, parent, nodeType, nodeNumber, null, '?', Support.Green.RelationSideType.None);
        }
        /// <summary>
        /// Položka ve stromu tříd
        /// </summary>
        /// <param name="parent">Parent tohoto prvku. Root zde má null.</param>
        /// <param name="nodeType">Druh dat (třída / vztah)</param>
        /// <param name="nodeNumber">Číslo třídy / číslo vztahu</param>
        internal ClassTreeNode(ClassTreeNode parent, ClassTreeNodeType nodeType, int nodeNumber, Support.Green.RelationSideType relationSide)
        {
            this._Init(parent.ClassTreePanel, parent, nodeType, nodeNumber, null, '?', relationSide);
        }
        /// <summary>
        /// Položka ve stromu tříd
        /// </summary>
        /// <param name="parent">Parent tohoto prvku. Root zde má null.</param>
        /// <param name="nodeType">Druh dat (třída / vztah)</param>
        /// <param name="nodeNumber">Číslo třídy / číslo vztahu</param>
        /// <param name="fromRelationNumber">Tento záznam je Třída, a je navázaný v daném vztahu do stromu (nahoru).</param>
        /// <param name="relationSide">Strana vztahu, na které je tento záznam (pokud je to Třída) ve vztahu FromRelationNumber</param>
        internal ClassTreeNode(ClassTreeNode parent, ClassTreeNodeType nodeType, int nodeNumber, Int32? fromRelationNumber, RelationSideType relationSide)
        {
            this._Init(parent.ClassTreePanel, parent, nodeType, nodeNumber, fromRelationNumber, '?', relationSide);
        }
        /// <summary>
        /// Položka ve stromu tříd
        /// </summary>
        /// <param name="parent">Parent tohoto prvku. Root zde má null.</param>
        /// <param name="nodeType">Druh dat (třída / vztah)</param>
        /// <param name="nodeNumber">Číslo třídy / číslo vztahu</param>
        /// <param name="fromRelationNumber">Tento záznam je Třída, a je navázaný v daném vztahu do stromu (nahoru).</param>
        /// <param name="relationMark">Zkratka do TreePath: pro holou třídu bez vztahu = 'C', pro vztahy nebo třídu ze vztahu pak podle strany: 'R' / 'L', nebo pro pořadač 'F', a pro extended vztahy pak 'E'.</param>
        /// <param name="relationSide">Strana vztahu, na které je tento záznam (pokud je to Třída) ve vztahu FromRelationNumber</param>
        internal ClassTreeNode(ClassTreeNode parent, ClassTreeNodeType nodeType, int nodeNumber, Int32? fromRelationNumber, char relationMark, RelationSideType relationSide)
        {
            this._Init(parent.ClassTreePanel, parent, nodeType, nodeNumber, fromRelationNumber, relationMark, relationSide);
        }
        /// <summary>
        /// Inicializuje data v tomto node podle dodaných parametrů
        /// </summary>
        /// <param name="classTreePanel"></param>
        /// <param name="parent"></param>
        /// <param name="nodeType"></param>
        /// <param name="nodeNumber"></param>
        /// <param name="fromRelationNumber"></param>
        /// <param name="relationMark"></param>
        /// <param name="relationSide"></param>
        private void _Init(ClassTreePanel classTreePanel, ClassTreeNode parent, ClassTreeNodeType nodeType, int nodeNumber, Int32? fromRelationNumber, char relationMark, RelationSideType relationSide)
        {
            this.ClassTreePanel = classTreePanel;
            this.Parent = parent;
            this.NodeType = nodeType;
            this.NodeNumber = nodeNumber;
            this.FromRelationNumber = fromRelationNumber;
            this.RelationSide = relationSide;
            this._Childs = null;
            this.LoadSubNodesState = LoadSubItemStateType.NotLoaded;
            this.LoadSubNodesLock = new object();
            this.LoadItemsState = LoadSubItemStateType.NotLoaded;
            this.LoadItemsLock = new object();
            this._Attributes = null;
            switch (nodeType)
            {
                case ClassTreeNodeType.Class:
                    if (fromRelationNumber.HasValue)
                        this._InitForClassFromRelation();
                    else
                        this._InitForClass();
                    break;
                case ClassTreeNodeType.Relation:
                    this._InitForRelation();
                    break;
            }
            this.RelationMark = _GetRelationMark(relationMark);
            this.Nodes.Add(_GetWaitNode());              // U nodu bude svítit ikona [+], po otevření bude vidět tento node "...", a event NodeExpanded zajistí načtení reálných dat (vztahy, třídy: v threadu na pozadí)
        }
        /// <summary>
        /// Vrátí node se symbolem přesýpacích hodin a textem ...
        /// </summary>
        /// <returns></returns>
        private static TreeNode _GetWaitNode()
        {
            TreeNode node = new TreeNode("...");
            node.ImageKey = Components.ClassTreePanel.IMAGEKEY_WAIT;
            node.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_WAIT;
            node.ToolTipText = "Načítám data pro tuto úroveň...";
            return node;
        }
        /// <summary>
        /// Určí a vrátí hodnotu do RelationMark. Neukládá ji do něj.
        /// </summary>
        /// <param name="relationMark"></param>
        /// <returns></returns>
        private char _GetRelationMark(char relationMark)
        {
            if (this.NodeType == ClassTreeNodeType.Class && !this.FromRelationNumber.HasValue)
                return 'C';

            if (relationMark != '?')
                return relationMark;

            switch (this.RelationSide)
            {
                case RelationSideType.Left: return 'L';
                case RelationSideType.Right: return 'R';
            }
            return 'X';
        }
        /// <summary>
        /// Inicializuje node pro zobrazení třídy
        /// </summary>
        private void _InitForClass()
        {
            this._InitClassInfo(this.NodeNumber, true);
            this.ImageKey = Components.ClassTreePanel.IMAGE_REL_STATIC_TORIGHT_MASTER_TYPE;      // . IMAGEKEY_CLASS;
            this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;
        }
        /// <summary>
        /// Inicializuje node pro zobrazení třídy, ale pro případ kdy třída je ve vztahu, a je jediná - takže node pro vztah se vynechává, ale zobrazuje se jeho název.
        /// Třída pak v ikoně nese i informaci o vztahu (hlavičkový/položkový, statický/dynamický).
        /// </summary>
        private void _InitForClassFromRelation()
        {
            this._InitClassInfo(this.NodeNumber, true);                                          // Inicializace pro třídu, včetně textů
            string toolTipRelation = this.ToolTipText;
            this._InitRelationInfo(this.FromRelationNumber.Value, this.RelationSide, true);      // Inicializace pro vztah, včetně textů. Text v nodu zůstane jako pro vztah.
            this.ToolTipText = this.ToolTipText + Environment.NewLine + toolTipRelation;         // Kombinace textu tooltipu.

            // Ikona položky zobrazuje druh vztahu
            bool isExtendRelation = (this.RelationInfo == null);                                 // true pro extended vztahy
            bool isEntryRelation = (this.RelationInfo != null && this.RelationInfo.IsEntry);     // true pro položkové vztahy
            bool isDynamicRelation = (this.RelationInfo != null && this.RelationInfo.RelationStatDyn == Support.Green.RelationStatDynType.Dynamic);          // true pro dynamické vztahy (dynamické vztahy mají ikonu s řetízkem (LINK))
            bool isFromRight = (this.RelationSide == RelationSideType.Left);                     // true pro vztahy zprava doleva
            if (isExtendRelation)
            {   // Extended vztahy - neřešíme Statické/dynamické ani Položkové:
                this.ImageKey = Components.ClassTreePanel.IMAGEKEY_EXTEND_RELCLASS;
                this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;         // IMAGEKEY_EXTEND_RELCLASS_ACTIVE;
            }
            else if (!isEntryRelation)
            {   // Hlavičkové vztahy:
                if (!isDynamicRelation)
                {   // Statické vztahy z hlavičky:
                    // this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGEKEY_MASTER_RELCLASS_STAT_R : Components.ClassTreePanel.IMAGEKEY_MASTER_RELCLASS_STAT_L);
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_STATIC_TOLEFT_MASTER_TYPE : Components.ClassTreePanel.IMAGE_REL_STATIC_TORIGHT_MASTER_TYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;     // IMAGEKEY_CLASS_ACTIVE;
                }
                else
                {   // Dynamické vztahy z hlavičky:
                    // this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGEKEY_MASTER_RELCLASS_DYN_R : Components.ClassTreePanel.IMAGEKEY_MASTER_RELCLASS_DYN_L);
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TOLEFT_MASTER_TYPE : Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TORIGHT_MASTER_TYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;
                }
            }
            else
            {   // Položkové vztahy:
                if (!isDynamicRelation)
                {   // Statické vztahy z položky:
                    // this.ImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_RELCLASS_STAT;
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_STATIC_TOLEFT_ENTRIES_TYPE : Components.ClassTreePanel.IMAGE_REL_STATIC_TORIGHT_ENTRIES_TYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_ACTIVE;
                }
                else
                {   // Dynamické vztahy z položky:
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_TYPE : Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_TYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_ACTIVE;
                }
            }
            this.TreeNodeGroup = 1 + (isFromRight ? 0 /*1*/ : 0) + (isEntryRelation ? 2 : 0);
        }
        /// <summary>
        /// Inicializuje node pro zobrazení vztahu
        /// </summary>
        private void _InitForRelation()
        {
            this._InitRelationInfo(this.NodeNumber, this.RelationSide, true);
            bool isFromRight = (this.RelationSide == RelationSideType.Left);                     // true pro vztahy zprava doleva
            bool isEntryRelation = (this.RelationInfo != null && this.RelationInfo.IsEntry);     // true pro položkové vztahy
            bool isDynamicRelation = (this.RelationInfo != null && this.RelationInfo.RelationStatDyn == Support.Green.RelationStatDynType.Dynamic);          // true pro dynamické vztahy (dynamické vztahy mají ikonu s řetízkem (LINK))
            if (!isEntryRelation)
            {   // Hlavičkové vztahy:
                if (!isDynamicRelation)
                {   // Statické vztahy z hlavičky netypové:
                    // this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGEKEY_RELATION_STAT_R : Components.ClassTreePanel.IMAGEKEY_RELATION_STAT_L);
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_STATIC_TOLEFT_MASTER_NONTYPE : Components.ClassTreePanel.IMAGE_REL_STATIC_TORIGHT_MASTER_NONTYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;
                }
                else
                {   // Dynamické vztahy z hlavičky netypové:
                    // this.ImageKey = Components.ClassTreePanel.IMAGEKEY_RELATION_DYN;
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TOLEFT_MASTER_NONTYPE : Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TORIGHT_MASTER_NONTYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_CLASS_ACTIVE;
                }
            }
            else
            {   // Položkové vztahy:
                if (!isDynamicRelation)
                {   // Statické vztahy z položky netypové:
                    // this.ImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_RELCLASS_STAT;
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_STATIC_TOLEFT_ENTRIES_NONTYPE : Components.ClassTreePanel.IMAGE_REL_STATIC_TORIGHT_ENTRIES_NONTYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_ACTIVE;
                }
                else
                {   // Dynamické vztahy z položky netypové:
                    this.ImageKey = (isFromRight ? Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TOLEFT_ENTRIES_NONTYPE : Components.ClassTreePanel.IMAGE_REL_DYNAMIC_TORIGHT_ENTRIES_NONTYPE);
                    this.SelectedImageKey = Components.ClassTreePanel.IMAGEKEY_ENTRY_ACTIVE;
                }
            }
            this.TreeNodeGroup = 1 + (isFromRight ? 0 /*1*/ : 0) + (isEntryRelation ? 2 : 0);
        }
        /// <summary>
        /// Načte data o dané třídě, volitelně z nich uloží texty do this.Text a this.ToolTipText
        /// </summary>
        /// <param name="classNumber"></param>
        private void _InitClassInfo(int classNumber, bool setText)
        {
            ClassInfo classInfo;
            if (Repository.Classes.TryGetValue(classNumber, out classInfo))
            {
                this.ClassInfo = classInfo;
                if (setText)
                {
                    this.Text = classInfo.NazevTridy;
                    this.ToolTipText = GetToolTipText(classInfo);
                }
            }
            else
            {
                this.ClassInfo = null;
                if (setText)
                {
                    this.Text = "Třída " + classNumber.ToString() + " (nenalezena)";
                    this.ToolTipText = "Třída " + classNumber.ToString() + " nebyla v repozitory nalezena.";
                }
            }
        }
        /// <summary>
        /// Načte data o daném vztahu, volitelně z nich uloží texty do this.Text a this.ToolTipText
        /// </summary>
        /// <param name="classNumber"></param>
        private void _InitRelationInfo(int relationNumber, RelationSideType relationSide, bool setText)
        {
            RelationInfo relationInfo;
            bool isFromRight = false;
            bool isEntryRelation = false;
            if (Repository.Relations.TryGetValue(relationNumber, out relationInfo))
            {
                this.RelationInfo = relationInfo;
                isFromRight = (this.RelationSide == RelationSideType.Left);                     // true pro vztahy zprava doleva
                isEntryRelation = (this.RelationInfo != null && this.RelationInfo.IsEntry);     // true pro položkové vztahy
                if (setText)
                {
                    string name = relationInfo.NameFromSide(relationSide, true);
                    this.Text = name;
                    this.ToolTipText = GetToolTipText(relationInfo, relationSide);
                }
            }
            else
            {
                this.RelationInfo = null;
                if (setText)
                {
                    this.Text = "Vztah " + relationNumber.ToString() + " (nenalezen)";
                    this.ToolTipText = "Vztah " + relationNumber.ToString() + " nebyl v repozitory nalezen.";
                }
            }
            this.TreeNodeGroup = 1 + (isFromRight ? 0 /*1*/ : 0) + (isEntryRelation ? 2 : 0);
        }
        #endregion
        #region Tvorba ToolTipTextů
        internal static string GetToolTipText(ClassInfo classInfo)
        {
            return "Třída " + classInfo.CisloTridy.ToString() + ": " + classInfo.NazevTridy + Environment.NewLine +
                   "       " + (classInfo.IsEntries ? "Položková" : (classInfo.IsSubject ? "Subjektová" : "Nonsubjektová")) + "  (lcs." + classInfo.NazevTabulky + ")";
        }
        internal static string GetToolTipText(RelationInfo relationInfo, RelationSideType relationSide)
        {
            string name = relationInfo.NameFromSide(relationSide, true);
            string result = "Vztah " + relationInfo.CisloVztahu.ToString() + ": " + name + Environment.NewLine +
                   "       " + (relationInfo.RelationStatDyn == Support.Green.RelationStatDynType.Static ? "Statický vztah" : "Dynamický vztah") +
                   " " + (relationSide == RelationSideType.Right ? "zleva" : (relationSide == RelationSideType.Left ? "zprava" : ""));

            // Pokud má třída položky, zobrazím zda je vztah hlavičkový nebo položkový:
            ClassInfo sourceClassData = relationInfo.SourceClassData;
            if (sourceClassData != null && sourceClassData.HasEntries)
            {
                result += (relationInfo.IsMaster ? " hlavičkový" : (relationInfo.IsEntry ? " položkový" : ""));
            }

            // Pokud je vztah netypový, zobrazím to:
            if (relationInfo.TargetClasses.Count > 1)
                result += ", netypový vztah";

            return result;
        }
        internal static string GetToolTipText(Support.Green.NorisAttributesCls norisAttr)
        {
            return "Atribut: " + norisAttr.NazevCol + Environment.NewLine +
                   "       " + norisAttr.Dbname + " (" + norisAttr.Typ + ")";
        }
        #endregion
        #region Property
        /// <summary>
        /// Příznak, že tento node je root (nemá parenta)
        /// </summary>
        public bool IsRoot { get { return (this.Parent == null); } }
        /// <summary>
        /// Reference na panel ClassTree, v němž jsme zobrazení.
        /// Panel poskytuje přístup k ClassTreeExtenderu.
        /// </summary>
        internal ClassTreePanel ClassTreePanel { get; private set; }
        /// <summary>
        /// Parent tohoto prvku. Root zde má odkaz null.
        /// </summary>
        public new ClassTreeNode Parent { get; private set; }
        /// <summary>
        /// Druh tohoto nodu (třída / vztah)
        /// </summary>
        public ClassTreeNodeType NodeType { get; private set; }
        /// <summary>
        /// Číslo třídy / číslo vztahu
        /// </summary>
        public int NodeNumber { get; private set; }
        /// <summary>
        /// Číslo vztahu, který nás přivedl na tuto třídu
        /// </summary>
        public Int32? FromRelationNumber { get; private set; }
        /// <summary>
        /// Data o třídě, pokud je zde uložena třída (this.NodeType = Class)
        /// </summary>
        public ClassInfo ClassInfo { get; private set; }
        /// <summary>
        /// Data o vztahu:
        /// buď vztah, který je zde zachycen - pokud this.NodeType == Relation, 
        /// anebo vztah který vede na zdejší třídu - když this.NodeType == Class)
        /// </summary>
        public RelationInfo RelationInfo { get; private set; }
        /// <summary>
        /// Značka vztahu (R = zleva doprava, L = zleva doprava)
        /// </summary>
        internal char RelationMark { get; private set; }
        /// <summary>
        /// Číslo skupiny ve stromu: 1=Master zleva doprava, 2=Master zprava doleva, 3=Entries zleva doprava, 4=Entries zprava doleva
        /// </summary>
        internal int TreeNodeGroup { get; private set; }
        /// <summary>
        /// Strana vztahu, na které je tento záznam.
        /// Příklad: pokud ze třídy A ukazuje statický vztah doprava na číselník B, a tato instance odpovídá onomu číselníku B, 
        /// pak je zde v RelationSide uložena strana Right (číselník je vpravo).
        /// Pokud ale třída A je záznam s daty, kde v dynamickém vztahu je záznam B, a tato instance odpovídá záznamu B, který je ve vztahu Vlevo, 
        /// pak je zde v RelationSide uložena strana Left (záznam je vlevo).
        /// </summary>
        public RelationSideType RelationSide { get; private set; }
        /// <summary>
        /// Soupis přímých potomků ve stromu.
        /// Autoinicializační (On-demand) property.
        /// </summary>
        public List<ClassTreeNode> Childs
        {
            get { this._CheckLoadedChilds(); return this._Childs; }
        }
        private List<ClassTreeNode> _Childs;
        /// <summary>
        /// Stav načítání Childs (SubNodes).
        /// Načítání probíhá v threadu na pozadí.
        /// </summary>
        protected LoadSubItemStateType LoadSubNodesState;
        /// <summary>
        /// Zámek pro lock při testu před zahájením načítání SubNodes
        /// </summary>
        protected object LoadSubNodesLock;
        /// <summary>
        /// Stav načítání Attributes (List.Items). 
        /// Načítání probíhá v threadu na pozadí.
        /// </summary>
        protected LoadSubItemStateType LoadItemsState;
        /// <summary>
        /// Zámek pro lock při testu před zahájením načítání Items
        /// </summary>
        protected object LoadItemsLock;
        /// <summary>
        /// Soupis atributů ve vhodném třídění, atributy lze přímo vkládat do ListView.Items
        /// Autoinicializační (On-demand) property.
        /// </summary>
        public List<ClassTreeAttributeItem> Attributes
        {
            get { this._CheckLoadedAttributes(); return this._Attributes; }
        }
        private List<ClassTreeAttributeItem> _Attributes;
        /// <summary>
        /// Příznak, že tento node obsahuje subnodes (položky Childs).
        /// Zjištění (čtení) této hodnoty může vyvolat načtení dat do Childs, pokud dosud nebyla načtena.
        /// </summary>
        public bool ContainChilds { get { return (this.Childs.Count > 0); } }
        /// <summary>
        /// Obsahuje jméno tohoto nodu, bez jeho Parentů.
        /// </summary>
        public string TreePath { get { return this._GetTreePath(); } }
        /// <summary>
        /// Obsahuje plné jméno tohoto nodu.
        /// Toto jméno v sobě nese cestu od rootu přes vztahy a třídy až k danému záznamu.
        /// Jedná se o sekvenci položek, oddělenou dvojtečkami. Každá položka začíná písmenem a za ní následuje číselný údaj (kladný, celočíselný).
        /// Plné jméno nekončí ani nezačíná dvojtečkou, ale může ji mít uvnitř.
        /// Písmeno značí: 
        /// C=třída (Class),
        /// R=vztah doprava (příklad: z položky dokladu na kmenovou kartu zboží)
        /// L=vztah doleva (příklad: z kmenové karty zboží na položku dokladu)
        /// Číselný údaj = číslo třídy nebo vztahu.
        /// </summary>
        public string FullTreePath { get { return this._GetFullTreePath(); } }
        /// <summary>
        /// Proměnná, která popisuje zdejší číslo záznamu, (cislo_subjektu / cislo_nonsubjektu), včetně plné cesty k němu.
        /// Typický obsah: «{C1188:R8942:C1190:Master.cislo_nonsubjektu}»
        /// </summary>
        public string CurrentVariableRecordNumber { get { return this._GetCurrentVariableRecordNumber(); } }
        /// <summary>
        /// Příkaz SELECT, který obsahuje aktuální tabulky.
        /// Používá se jako obsah pro vložení výrazu do editoru poté, kdy uživatel klikne na atribut "Výraz...".
        /// Typický obsah: «["SELECT ??? FROM lcs.product_order master JOIN lcs.subjekty subjekt on subjekt.cislo_subjektu = master.cislo_subjektu WHERE master.cislo_subjektu = «{proměnná obsahující record_number}»]»
        /// </summary>
        public string CurrentExpressionSelect { get { return this._GetCurrentExpressionSelect(); } }
        /// <summary>
        /// Stav načítání Childs do tohoto nodu
        /// </summary>
        protected enum LoadSubItemStateType
        {
            /// <summary>Neurčeno</summary>
            None = 0,
            /// <summary>Nenačteno</summary>
            NotLoaded,
            /// <summary>Byl vydán požadavek na načítání, ještě se nezačalo</summary>
            WaitToLoad,
            /// <summary>Právě se načítá</summary>
            NowLoading,
            /// <summary>Načteno</summary>
            Loaded
        }
        #endregion
        #region Private metody pro podporu tvorby property (Treepath, Expression atd)
        /// <summary>
        /// Vrací jméno tohoto nodu, bez jeho Parentů.
        /// </summary>
        /// <returns></returns>
        private string _GetTreePath()
        {
            string treePath = "";
            switch (this.NodeType)
            {
                case ClassTreeNodeType.Class:
                    treePath = _GetRelationPath();
                    treePath += (treePath.Length == 0 ? "" : ":") + "C" + this.ClassInfo.CisloTridy.ToString();
                    break;
                case ClassTreeNodeType.Relation:
                    treePath = _GetRelationPath();
                    break;
            }
            return treePath;
        }
        /// <summary>
        /// Vrací plné jméno tohoto nodu včetně všech parent nodů.
        /// Plné jméno nekončí ani nezačíná dvojtečkou, ale může ji mít uvnitř.
        /// </summary>
        /// <returns></returns>
        private string _GetFullTreePath()
        {
            string treePath = this._GetTreePath();
            if (!this.IsRoot)
            {   // Všichni moji dobří předchůdci
                string parentPath = this.Parent.FullTreePath;
                if (parentPath.Length > 0)
                    treePath = parentPath + ":" + treePath;
            }
            return treePath;
        }
        /// <summary>
        /// Vrátí segment cesty TreePath za zdejší Relation (bez ohledu na this.NodeType).
        /// Akceptuje data this.RelationInfo a this.RelationSide.
        /// </summary>
        /// <returns></returns>
        private string _GetRelationPath()
        {
            if (this.RelationInfo != null)
            {
                switch (this.RelationSide)
                {
                    case Support.Green.RelationSideType.Right:
                        return "R" + this.RelationInfo.CisloVztahu.ToString();
                    case Support.Green.RelationSideType.Left:
                        return "L" + this.RelationInfo.CisloVztahu.ToString();
                }
            }
            else if (this.FromRelationNumber.HasValue)
            {
                return this.RelationMark.ToString() + this.FromRelationNumber.Value.ToString();
            }
            return "";
        }
        /// <summary>
        /// Vrací proměnnou, která popisuje zdejší číslo záznamu, (cislo_subjektu / cislo_nonsubjektu), včetně plné cesty k němu.
        /// Typický obsah: «{C1188:R8942:C1190:Master.cislo_nonsubjektu}»
        /// </summary>
        private string _GetCurrentVariableRecordNumber()
        {
            string variable = "";
            switch (this.NodeType)
            {
                case ClassTreeNodeType.Class:
                    variable = this._GetCurrentVariableRecordNumberClass();
                    break;
                case ClassTreeNodeType.Relation:
                    variable = this._GetCurrentVariableRecordNumberRelation();
                    break;
            }
            return Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_VAR_BEGIN +
                   variable +
                   Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_VAR_END;
        }
        /// <summary>
        /// Vrátí proměnnou, která popisuje zdejší číslo záznamu (bez krajních oddělovačů) pro případ, kdy this je typu ClassTreeNodeType.Class
        /// </summary>
        /// <returns></returns>
        private string _GetCurrentVariableRecordNumberClass()
        {
            string columnName = "record_number";
            if (this.ClassInfo != null)
            {
                ClassTreeAttributeItem keyAttr = this.Attributes.FirstOrDefault(a => a.GroupType == AttributeGroupType.RecordNumber);   // Najdu atribut, který nese číslo záznamu
                if (keyAttr != null)
                    columnName = keyAttr.ShortVariableName;
            }
            return this.FullTreePath + ":" + columnName;
        }
        /// <summary>
        /// Vrátí proměnnou, která popisuje zdejší číslo záznamu (bez krajních oddělovačů) pro případ, kdy this je typu ClassTreeNodeType.Relation
        /// </summary>
        /// <returns></returns>
        private string _GetCurrentVariableRecordNumberRelation()
        {
            string columnName = "record_number";
            if (this.ClassInfo != null)
            {
                ClassTreeAttributeItem keyAttr = this.Attributes.FirstOrDefault(a => a.GroupType == AttributeGroupType.RecordNumber);   // Najdu atribut, který nese číslo záznamu
                if (keyAttr != null)
                    columnName = keyAttr.ShortVariableName;
            }
            return this.FullTreePath + ":" + columnName;
        }
        /// <summary>
        /// Vrací příkaz SELECT, který obsahuje aktuální tabulky.
        /// Používá se jako obsah pro vložení výrazu do editoru poté, kdy uživatel klikne na atribut "Výraz...".
        /// Typický obsah: «["SELECT ??? FROM lcs.product_order master JOIN lcs.subjekty subjekt on subjekt.cislo_subjektu = master.cislo_subjektu WHERE master.cislo_subjektu = «{proměnná obsahující record_number}»]»
        /// </summary>
        /// <returns></returns>
        private string _GetCurrentExpressionSelect()
        {
            string select = "";
            switch (this.NodeType)
            {
                case ClassTreeNodeType.Class:
                    select = this._GetCurrentExpressionSelectClass();
                    break;
                case ClassTreeNodeType.Relation:
                    select = this._GetCurrentExpressionSelectRelation();
                    break;
            }
            return Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_SQL_BEGIN +
                   select +
                   Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_SQL_END;
        }
        /// <summary>
        /// Vrátí návrh SELECTU pro aktuální node.
        /// Návrh obsahuje FROM ... JOIN pro aktuální třídu.
        /// </summary>
        /// <returns></returns>
        private string _GetCurrentExpressionSelectClass()
        {
            string from = "";
            if (this.ClassInfo != null)
            {
                from = "FROM " + this.ClassInfo.GetTableName(ClassTableType.Master) + " master ";
                if (this.ClassInfo.IsSubject)
                {
                    from += "JOIN lcs.subjekty subjekt ON subjekt.cislo_subjektu = master.cislo_subjektu ";
                    if (this.ClassInfo.HasEntries)
                        from += "LEFT OUTER JOIN " + this.ClassInfo.TabulkaObjects + " entries ON entries.cislo_subjektu = master.cislo_subjektu";
                }
            }
            else
            {
                from = "FROM (table_for_class_" + this.NodeNumber.ToString() + ") master ";
            }

            ClassTreeAttributeItem keyAttr = this.Attributes.FirstOrDefault(a => a.GroupType == AttributeGroupType.RecordNumber);   // Najdu atribut, který nese číslo záznamu
            string where = "WHERE master." +
                           (keyAttr == null ? "identity_key" : keyAttr.Column) +
                           " = " +
                           this.CurrentVariableRecordNumber;

            return "SELECT ??? " + from + where;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string _GetCurrentExpressionSelectRelation()
        {
            return "";
        }
        #endregion
        #region OnDemand načítání Childs a Attributes
        /// <summary>
        /// Metoda ověří, zda již jsou načteny položky Childs (do kolekce this.Nodes), a pokud ne tak je nyní načte.
        /// </summary>
        internal void CheckLoadedChilds()
        {
            this._CheckLoadedChilds();
        }
        /// <summary>
        /// Metoda ověří, zda již jsou načteny položky Attributes (do kolekce this.Attributes), a pokud ne tak je nyní načte.
        /// </summary>
        internal void CheckLoadedAttributes()
        {
            this._CheckLoadedAttributes();
        }
        /// <summary>
        /// Metoda zajistí přípravu a odeslání atributů do metody this.ClassTreePanel.FetchAttributeList
        /// </summary>
        internal void SendAttributeList()
        {
            switch (this.LoadItemsState)
            {
                case LoadSubItemStateType.None:
                case LoadSubItemStateType.NotLoaded:
                    this._CheckLoadedAttributes();
                    break;
                case LoadSubItemStateType.WaitToLoad:
                case LoadSubItemStateType.NowLoading:
                    break;
                case LoadSubItemStateType.Loaded:
                    this.ClassTreePanel.FetchAttributeList(this);
                    break;
            }
        }
        /// <summary>
        /// Metoda vrátí seznam položek do ListView atributů.
        /// </summary>
        /// <param name="listView"></param>
        /// <returns></returns>
        internal ListViewItem[] GetAttributesItem(ListView listView)
        {
            List<ListViewItem> items = new List<ListViewItem>();
            foreach (ClassTreeAttributeItem classAttrItem in this.Attributes)
                items.Add(classAttrItem.CreateListViewItem(listView));
            return items.ToArray();
        }
        /// <summary>
        /// Vrací true, pokud podle daného stavu není třeba načítat data
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        private static bool _SkipLoading(LoadSubItemStateType state)
        {
            return (state == LoadSubItemStateType.WaitToLoad || 
                    state == LoadSubItemStateType.NowLoading || 
                    state == LoadSubItemStateType.Loaded);
        }
        #region Načítání Childs nodes do stromu tříd - řízení kontroly načítání a zahájení načítání na pozadí
        /// <summary>
        /// Metoda ověří, zda již jsou načteny položky Childs = podřízené vztahy a třídy (do kolekce this.Childs), a pokud ne tak je nyní načte.
        /// </summary>
        private void _CheckLoadedChilds()
        {
            // Pokud už je načítání zařízeno, skončíme:
            if (_SkipLoading(this.LoadSubNodesState)) return;

            lock (this.LoadSubNodesLock)
            {   // Před chvilkou ještě nebylo načteno. Možná začneme načítat my. Pokud již někdo jiný nenačítá...
                if (_SkipLoading(this.LoadSubNodesState)) return;
                this.LoadSubNodesState = LoadSubItemStateType.WaitToLoad;
                Noris.Schedule.Support.Core.WorkingThread.AddItem(this._LoadSubNodesRun, this._LoadSubNodesDone);   // Zajistí načítání dat v threadu na pozadí
            }
        }
        /// <summary>
        /// Metoda načítání dat - běží v threadu na pozadí. Nesmí přímo pracovat s vizuálními property objektu.
        /// </summary>
        private void _LoadSubNodesRun()
        {
            this.LoadSubNodesState = LoadSubItemStateType.NowLoading;
            List<ClassTreeNode> childs = new List<ClassTreeNode>();
            // Moje Childs jsou vztažené třídy anebo názvy vztahů, pokud pro daný vztah je v cílových pořadačích více různých tříd (netypový vztah).
            switch (this.NodeType)
            {
                case ClassTreeNodeType.Class:
                    childs = this._GetTargetChildsForClass();
                    break;
                case ClassTreeNodeType.Relation:
                    childs = this._GetTargetChildsForRelation();
                    break;
                default:
                    childs = new List<ClassTreeNode>();
                    break;
            }
            this._Childs = childs;
        }
        /// <summary>
        /// Metoda vyvolaná po dokončení načítání dat, v threadu na pozadí
        /// </summary>
        private void _LoadSubNodesDone()
        {
            if (this.ClassTreePanel.InvokeRequired)
                this.ClassTreePanel.BeginInvoke(new Action(this._LoadSubNodesDone)); //  (delegate() { this._LoadChildDone(); });
            else
            {
                this.Nodes.Clear();
                if (this._Childs != null)
                    this.Nodes.AddRange(this._Childs.ToArray());
                this.LoadSubNodesState = LoadSubItemStateType.Loaded;
            }
        }
        #endregion
        #region Fyzické načítání Childs nodes do stromu tříd
        /// <summary>
        /// Načte a vrátí data do Childs pro případ, kdy this je obrazem třídy (nikoli vztahu).
        /// </summary>
        /// <returns></returns>
        private List<ClassTreeNode> _GetTargetChildsForClass()
        {
            List<ClassTreeNode> childs = new List<ClassTreeNode>();

            // Externí vztahy (a případné potlačení standardních vztahů), definuje ClassTreeExtender:
            ClassTreeExtenderGetRelationsArgs args = new ClassTreeExtenderGetRelationsArgs(this.NodeNumber, this.ClassTreePanel.RelationToLeftEnabled);
            if (this.ClassTreePanel.ClassTreeExtender != null)
                this.ClassTreePanel.ClassTreeExtender.GetExtendedRelations(args);
            // Vložit externí vztahy:
            foreach (ClassExtendRelation extRel in args.ExtendedRelations)
            {
                ClassTreeNode child = new ClassTreeNode(this, ClassTreeNodeType.Class, extRel.TargetClass, extRel.Number, extRel.RelationMark, Support.Green.RelationSideType.Right);
                child.Text = extRel.Name;
                child.ToolTipText = extRel.ToolTipText + Environment.NewLine + GetToolTipText(child.ClassInfo);
                childs.Add(child);
            }

            // Vložit standardní vztahy (pokud to není globálně zakázané extenderem):
            if (!args.HideGreenRelationsAll && this.ClassInfo != null)
            {
                List<RelationSideInfo> relations = new List<RelationSideInfo>();

                // Vztahy doprava (například z hlavičky dokladu na záznam organizace):
                relations.AddRange(this.ClassInfo.RelationSideLeftList);

                // Vztahy doleva (například z organizace na všechny hlavičky dokladů, které ji používají):
                if (this.ClassTreePanel.RelationToLeftEnabled)
                    relations.AddRange(this.ClassInfo.RelationSideTargetList);

                if (relations != null && relations.Count > 0)
                {   // Vztahy z dané třídy:
                    // relations.Sort(RelationSideInfo.CompareByTypeName);
                    foreach (RelationSideInfo relation in relations)
                    {   // Pokud se tento vztah má přidat do stromu vztahů (tj. není zakázaný ClassTree extenderem):
                        if (_CanAddGreenRelation(args, relation))
                        {   // Kam vztah pokačuje: Doprava nebo Doleva?
                            switch (relation.Side)
                            {
                                case RelationSideType.Right:
                                    // Vztah vede zleva (ze záznamu) doprava (například na číselník):
                                    this._AddChildToRight(relation, childs);
                                    break;

                                case RelationSideType.Left:
                                    // Vztah vede zprava (například číselník) doleva (například doklad, který ukazuje na ten číselník):
                                    this._AddChildToLeft(relation, childs);
                                    break;
                            }
                        }
                    }
                }
            }

            if (childs.Count > 1)
                childs.Sort(ClassTreeNode.CompareByTypeText);

            return childs;
        }
        /// <summary>
        /// Přidá do pole childs další node, který odpovídá danému vztahu.
        /// Vztah vede zleva (ze záznamu) doprava (například na číselník):
        /// </summary>
        /// <param name="relation"></param>
        /// <param name="childs"></param>
        private void _AddChildToRight(RelationSideInfo relation, List<ClassTreeNode> childs)
        {
            // Pro jeden konkrétní vztah načtu jeho cílové třídy a zjistím tak, zda je/není netypový:
            List<ClassInfo> targetClasses = relation.RelationInfo.TargetClasses;
            int targetCount = targetClasses.Count;
            if (targetCount == 1)
            {   // Cílem je jedna třída: vložit ji do Childs (třída pod třídou), ale tvářit se musí jako vztah:
                childs.Add(new ClassTreeNode(this, ClassTreeNodeType.Class, targetClasses[0].CisloTridy, relation.CisloVztahu, relation.Side));
            }
            else if (targetCount > 1)
            {   // Cílem je více tříd, proto jako Child vložíme vztah, a do něj vložíme všechny target classes:
                ClassTreeNode child = new ClassTreeNode(this, ClassTreeNodeType.Relation, relation.CisloVztahu, relation.Side);
                childs.Add(child);
                targetClasses.Sort(ClassInfo.CompareByName);

                // Do nového nodu (za tento vztah) přidáme jeho podřízené třídy. Tedy: nahrazujeme činnost donačítání subnodes, takže se vším všudy:
                child.Nodes.Clear();                                // V Nodes existoval subnode Wait (s přesýpacími hodinami). Vyčistéme to.
                foreach (ClassInfo targetClassInfo in targetClasses)
                    child._AddChild(ClassTreeNodeType.Class, targetClassInfo.CisloTridy);
                child.LoadSubNodesState = LoadSubItemStateType.Loaded;   // Childs jsou načtené.
            }
        }
        /// <summary>
        /// Přidá do pole childs další node, který odpovídá danému vztahu.
        /// Vztah vede zprava (například číselník) doleva (například doklad, který ukazuje na ten číselník):
        /// </summary>
        /// <param name="relation"></param>
        /// <param name="childs"></param>
        private void _AddChildToLeft(RelationSideInfo relation, List<ClassTreeNode> childs)
        {
            // Vztah zprava (typicky vpravo je doklad, svým statickým vztahem ukazuje na mě jako na číselník) je vždycky typový.
            // To proto, že vztah je definován ZE třídy nebo Z pořadače, nikdy ne z více tříd !
            // Třída "vpravo" je tedy například ten doklad, z něhož je vztah deklarován.
            // Vpravo může být namísto třídy uveden pořadač, pak ale do stromu tříd doprava dám jeho třídu:
            int cisloTridy = 0;
            if (!relation.RelationInfo.CisloTridy.IsNull)
                cisloTridy = relation.RelationInfo.CisloTridy.Value;
            else if (!relation.RelationInfo.CisloPoradace.IsNull)
            {
                FolderInfo folder = Noris.Schedule.Support.Green.Repository.Folders[relation.RelationInfo.CisloPoradace.Value];
                if (folder != null)
                    cisloTridy = folder.CisloTridy;
            }
            if (cisloTridy != 0)
                childs.Add(new ClassTreeNode(this, ClassTreeNodeType.Class, cisloTridy, relation.CisloVztahu, relation.Side));
        }
        /// <summary>
        /// Načte a vrátí data do Childs pro případ, kdy this je obrazem vztahu (nikoli třídy).
        /// </summary>
        /// <returns></returns>
        private List<ClassTreeNode> _GetTargetChildsForRelation()
        {
            List<ClassTreeNode> childs = new List<ClassTreeNode>();

            if (this.RelationInfo != null)
            {
                List<ClassInfo> targetClasses = this.RelationInfo.TargetClasses;
                foreach (ClassInfo targetClassInfo in targetClasses)
                    childs.Add(new ClassTreeNode(this, ClassTreeNodeType.Class, targetClassInfo.CisloTridy));
            }

            return childs;
        }
        /// <summary>
        /// Metoda pro přidání jednoho dalšího Child.
        /// Využívá se při načítání Childs do našeho Parenta, když přitom Parent zjistí, že Child je Relation a přitom zná cílové třídy.
        /// </summary>
        /// <param name="classTreeNodeType"></param>
        /// <param name="targetClass"></param>
        private void _AddChild(ClassTreeNodeType nodeType, int nodeNumber)
        {
            if (this._Childs == null)
                this._Childs = new List<ClassTreeNode>();

            ClassTreeNode child = new ClassTreeNode(this, nodeType, nodeNumber);
            this._Childs.Add(child);
            this.Nodes.Add(child);
        }
        /// <summary>
        /// Komparátor prvků
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        protected static int CompareByTypeText(ClassTreeNode a, ClassTreeNode b)
        {
            // V určeném pořadí skupin (TreeNodeGroup):
            int cmp = a.TreeNodeGroup.CompareTo(b.TreeNodeGroup);
            if (cmp != 0) return cmp;

            // Podle textu:
            return a.Text.CompareTo(b.Text);
        }
        #endregion
        #region Načítání Items do seznamu atributů. Ačkoliv je rychlé, musím provést načítání na pozadí - kvůli synchronizaci využití SqlConnection.
        /// <summary>
        /// Zajistí, že budou načteny atributy pro tuto položku.
        /// </summary>
        private void _CheckLoadedAttributes()
        {
            // Pokud už je načítání zařízeno, skončíme:
            if (_SkipLoading(this.LoadItemsState)) return;

            lock (this.LoadItemsLock)
            {   // Před chvilkou ještě nebylo načteno. Možná začneme načítat my. Pokud již někdo jiný nenačítá...
                if (_SkipLoading(this.LoadItemsState)) return;
                this.LoadItemsState = LoadSubItemStateType.WaitToLoad;
                Noris.Schedule.Support.Core.WorkingThread.AddItem(this._LoadItemsRun, this._LoadItemsDone);         // Zajistí načítání dat v threadu na pozadí = synchronně s načítáním SubNodes
            }
        }
        /// <summary>
        /// Metoda načítání dat Items = Attributes - běží v threadu na pozadí. Nesmí přímo pracovat s vizuálními property objektu.
        /// </summary>
        private void _LoadItemsRun()
        {
            this.LoadItemsState = LoadSubItemStateType.NowLoading;
            List<ClassTreeAttributeItem> attributes = new List<ClassTreeAttributeItem>();
            switch (this.NodeType)
            {
                case ClassTreeNodeType.Class:
                    attributes = this._GetAttributesForClass();
                    break;
                case ClassTreeNodeType.Relation:
                    attributes = this._GetAttributesForRelation();
                    break;
            }
            this._Attributes = attributes;
            this.LoadItemsState = LoadSubItemStateType.Loaded;
        }
        /// <summary>
        /// Metoda vyvolaná po dokončení načítání dat Items = Attributes, v threadu na pozadí
        /// </summary>
        private void _LoadItemsDone()
        {
            this.ClassTreePanel.FetchAttributeList(this);
        }
        /// <summary>
        /// Načte a vrátí atributy pro sebe jakožto třídu
        /// </summary>
        /// <returns></returns>
        private List<ClassTreeAttributeItem> _GetAttributesForClass()
        {
            List<ClassTreeAttributeItem> attributes = new List<ClassTreeAttributeItem>();
            bool addIdentityColumn = false;
            string identityColumn = null;

            // Externí atributy (a případné potlačení standardních atributů), definuje ClassTreeExtender:
            ClassTreeExtenderGetAttributesArgs args = new ClassTreeExtenderGetAttributesArgs(this.NodeNumber);
            if (this.ClassTreePanel.ClassTreeExtender != null)
                this.ClassTreePanel.ClassTreeExtender.GetExtendedAttributes(args);
            // Vložit externí atributy:
            foreach (ClassExtendAttribute extAttr in args.ExtendedAttributes)
            {
                ClassTreeAttributeItem attr = new ClassTreeAttributeItem(this, null, AttributeGroupType.Extended, extAttr.Name, extAttr.Column, extAttr.ToolTipText);
                attributes.Add(attr);
            }

            // Standardní atributy Noris:
            if (this.ClassInfo != null)
            {
                addIdentityColumn = true;
                List<NorisAttributesCls> norisAttrList = this.ClassInfo.AttributesList;
                norisAttrList.Sort(NorisAttributesCls.CompareByTypeName);
                identityColumn = this.ClassInfo.GetIdentityColumn(Support.Green.ClassTableType.Master);   // "cislo_subjektu", "cislo_nonsubjektu"
                foreach (NorisAttributesCls norisAttr in norisAttrList)
                {
                    AttributeGroupType group = AttributeGroupType.None;
                    string dbColumn = norisAttr.Column;

                    if (String.Equals(identityColumn, dbColumn, StringComparison.OrdinalIgnoreCase))
                    {
                        group = AttributeGroupType.RecordNumber;
                        addIdentityColumn = false;
                    }
                    else
                    {
                        ClassTableType classTableType = norisAttr.TableType;                              // Do které tabulky (explicitní typ) patří tento atribut
                        group = _GetGroupFromTableType(classTableType);                                   // Skupina v Listu (zjednodušená oproti ClassTableType)
                    }
                    if (group != AttributeGroupType.None && _CanAddGreenAttribute(args, group, dbColumn))
                    {
                        ClassTreeAttributeItem attr = new ClassTreeAttributeItem(this, norisAttr, group, norisAttr.NazevCol, dbColumn, GetToolTipText(norisAttr));
                        attributes.Add(attr);
                    }
                }
            }

            // Přidat "Klíč záznamu":
            if (addIdentityColumn && _CanAddGreenAttribute(args, AttributeGroupType.RecordNumber, identityColumn))
            {
                string identityTip = "lcs." + this.ClassInfo.NazevTabulky + "." + identityColumn + " (int)";
                attributes.Add(new ClassTreeAttributeItem(this, null, AttributeGroupType.RecordNumber, "Klíč tohoto záznamu", identityColumn, identityTip));
            }

            // Přidat "Výraz..."
            if (_CanAddGreenAttribute(args, AttributeGroupType.RecordNumber, null))
                attributes.Add(new ClassTreeAttributeItem(this, null, AttributeGroupType.Expression, "Výraz...", "SELECT ???", "Umožní zadat SQL výraz, který vrátí hodnotu do tohoto místa"));

            return attributes;
        }
        /// <summary>
        /// Zjistí, zda se do seznamu atributů má přidat atribut dané skupiny a jména, podle dat určených extenderem (ten může něco zakázat = skrýt atributy Green).
        /// </summary>
        /// <param name="args"></param>
        /// <param name="group"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        private bool _CanAddGreenAttribute(ClassTreeExtenderGetAttributesArgs args, AttributeGroupType group, string column)
        {
            if (args.HideGreenAttributesAll) return false;
            if (args.HideGreenAttributesGroup.Contains(group)) return false;
            if (column != null && args.HideGreenAttributes.Any(a => a.GroupType == group && String.Equals(a.Column, column, StringComparison.InvariantCultureIgnoreCase))) return false;
            return true;
        }
        /// <summary>
        /// Zjistí, zda se do seznamu vztahů má přidat daný vztah, podle dat určených extenderem (ten může něco zakázat = skrýt vztahy Green).
        /// </summary>
        /// <param name="args"></param>
        /// <param name="relationSideInfo"></param>
        /// <returns></returns>
        private bool _CanAddGreenRelation(ClassTreeExtenderGetRelationsArgs args, RelationSideInfo relationSideInfo)
        {
            if (args.HideGreenRelationsAll) return false;
            if (args.HideGreenRelations.Contains(relationSideInfo.RelationInfo.CisloVztahu)) return false;
            return true;
        }
        /// <summary>
        /// Vrátí grupu v seznamu atributů podle typu tabulky (pro atribut)
        /// </summary>
        /// <param name="classTableType"></param>
        /// <returns></returns>
        private AttributeGroupType _GetGroupFromTableType(Support.Green.ClassTableType classTableType)
        {
            switch (classTableType)
            {
                case Support.Green.ClassTableType.Subjekt: return AttributeGroupType.Subjekt;
                case Support.Green.ClassTableType.Master: return AttributeGroupType.Master;
                case Support.Green.ClassTableType.Entries: return AttributeGroupType.Entries;
                case Support.Green.ClassTableType.UdaMaster: return AttributeGroupType.MasterUda;
                case Support.Green.ClassTableType.UdaEntries: return AttributeGroupType.EntriesUda;
            }
            return AttributeGroupType.System;
        }
        /// <summary>
        /// Vrátí atributy pro položku typu Vztah (ze vztahu lze navázat pouze lcs.subjekty)
        /// </summary>
        /// <returns></returns>
        private List<ClassTreeAttributeItem> _GetAttributesForRelation()
        {
            List<ClassTreeAttributeItem> attributes = new List<ClassTreeAttributeItem>();

            attributes.Add(new ClassTreeAttributeItem(this, null, AttributeGroupType.Subjekt, "Reference", "reference_subjektu", "Uživatelské číslo"));
            attributes.Add(new ClassTreeAttributeItem(this, null, AttributeGroupType.Subjekt, "Název", "nazev_subjektu", "Uživatelské číslo"));
            attributes.Add(new ClassTreeAttributeItem(this, null, AttributeGroupType.RecordNumber, "Číslo subjektu", "cislo_subjektu", "Klíčové číslo"));

            return attributes;
        }
        #endregion
        #endregion
        #region enum ClassTreeNodeType
        /// <summary>
        /// Druh položky (třída / vztah)
        /// </summary>
        public enum ClassTreeNodeType
        {
            Class,
            Relation
        }
        #endregion
    }
    #endregion
    #region class ClassTreeAttributeItem : jedna položka Listu, která v sobě nese navíc informaci o atributech třídy. Nejde o nativního potomka ListViewItem, ale dokáže jej vytvořit.
    /// <summary>
    /// ClassTreeAttributeItem : jedna položka Listu, která v sobě nese navíc informaci o atributech třídy. Nejde o nativního potomka ListViewItem, ale dokáže jej vytvořit.
    /// </summary>
    public class ClassTreeAttributeItem : ClassExtendAttribute
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="norisAttributes"></param>
        /// <param name="groupType"></param>
        /// <param name="text"></param>
        /// <param name="name"></param>
        /// <param name="toolTip"></param>
        internal ClassTreeAttributeItem(ClassTreeNode parent, NorisAttributesCls norisAttributes, AttributeGroupType groupType, string text, string name, string toolTip)
            : base(groupType, text, name, toolTip)
        {
            this.Parent = parent;
            this.NorisAttributes = norisAttributes;
        }
        public override string ToString()
        {
            return this.ExpressionText;
        }
        #endregion
        #region Standardní property
        /// <summary>
        /// Parent: reference na TreeNode, pro který je tato položka seznamu vytvořena
        /// </summary>
        public ClassTreeNode Parent { get; private set; }
        /// <summary>
        /// Informace o atributu. 
        /// Nemusí zde být vždy, pro atributy typu this.GroupType = Extended je zde null.
        /// </summary>
        public NorisAttributesCls NorisAttributes { get; private set; }
        #endregion
        #region Podpora pro tvorbu obsahu proměnné
        /// <summary>
        /// Název atributu.
        /// Obsahuje název skupiny atributu detailní (GroupType), tečku a název sloupce.
        /// </summary>
        public string AttributeKey { get { return this.GroupType.ToString() + "." + this.Column; } }
        /// <summary>
        /// Plná cesta k atributu.
        /// Obsahuje this.Parent.FullTreePath (tj. řadu Class:Relation:), oddělovač "větší než" &gt; , název skupiny atributu detailní (GroupType), tečku a název sloupce.
        /// </summary>
        public string FullTreePath { get { return this.Parent.FullTreePath + ATTRIBUTE_SEPARATOR + this.AttributeKey; } }
        /// <summary>
        /// Obsahuje deklaraci proměnné (Expression), která se vkládá do textu v případě, kdy uživatel vybral k zařazení do textu tuto položku.
        /// Tato property obsahuje FullPath.
        /// Obsahuje i vnější závorky, protože právě zde jsou k dispozici informace o druhu proměnné / SQL výrazu.
        /// </summary>
        public string ExpressionText
        {
            get
            {
                switch (this.GroupType)
                {
                    case AttributeGroupType.Subjekt:
                    case AttributeGroupType.Master:
                    case AttributeGroupType.MasterUda:
                    case AttributeGroupType.Entries:
                    case AttributeGroupType.EntriesUda:
                    case AttributeGroupType.Extended:
                    case AttributeGroupType.System:
                    case AttributeGroupType.RecordNumber:
                        return
                            Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_VAR_BEGIN +
                            this.Parent.FullTreePath + ":" + this.ShortVariableName +
                            Noris.Schedule.Support.Expressions.ParserExpressionEditorSetting.EXP_VAR_END;
                        return this.Parent.CurrentVariableRecordNumber;
                    case AttributeGroupType.Expression:
                        return this.Parent.CurrentExpressionSelect;
                }
                return "";
            }
        }
        /// <summary>
        /// Oddělovač v ClassTreeAttributeItem.FullTreePath mezi částí Class:Relation a Attribute.
        /// Má hodnotu "větší než" &gt;.
        /// </summary>
        public const string ATTRIBUTE_SEPARATOR = ">";
        #endregion
        #region Tvorba ListViewItem
        /// <summary>
        /// Vrátí nový objekt ListViewItem vytvořený ze svých dat.
        /// </summary>
        /// <param name="listView"></param>
        /// <returns></returns>
        internal ListViewItem CreateListViewItem(ListView listView)
        {
            ListViewItem item = new ListViewItem(this.Name);
            item.Tag = this;
            item.ToolTipText = this.ToolTipText;

            ListViewItem.ListViewSubItem subItem = new ListViewItem.ListViewSubItem(item, this.Column);
            item.SubItems.Add(subItem);

            string groupName = this.GroupName;
            if (listView != null && !String.IsNullOrEmpty(groupName))
                item.Group = listView.Groups[groupName];
            item.UseItemStyleForSubItems = true;
            return item;
        }
        #endregion
    }
    #endregion
}
